import ctypes as ct
import os


class AD9910:
    DLL_PATH = os.path.dirname(os.path.realpath(__file__))
    DLL = ct.WinDLL(f"{DLL_PATH}/AD9910.dll")
    RegLen = [4, 4, 4, 4, 4, 6, 6, 4,
              2, 4, 4, 8, 8, 4, 8, 8,
              8, 8, 8, 8, 8, 8, 4]

    def __init__(self):
        self.DLL.AD9910_Open()

    # ids: list of the IDs of the AD9910s connected to the PC
    def DDS_Init(self, ids):
        for i in ids:
            self.DLL.AD9910_Config(i)
            self.WriteReg(i, 1, b"\x01\x40\x08\xa0")
            self.WriteReg(i, 2, b"\x0d\x3f\xc0\xc8")
            self.WriteReg(i, 3, b"\x00\x00\x7f\xff")

    # Reserved
    def GetPort0(self, dds):
        cmd = ct.c_ubyte(0x15)
        ln = ct.c_ubyte(1)
        self.DLL.AD9910_Xfer(dds, 0x01, ct.byref(cmd), ct.byref(ln))
        self.DLL.AD9910_Xfer(dds, 0x81, ct.byref(cmd), ct.byref(ln))
        return cmd.value

    # Reserved
    def SetPort0(self, dds, val):
        cmd = ct.create_string_buffer(bytes([0x14, val]))
        ln = ct.c_ubyte(2)
        self.DLL.AD9910_Xfer(dds, 0x01, ct.byref(cmd), ct.byref(ln))

    # Send an IO_Update pulse to the designated DDS
    # dds: the ID of the DDS
    def IOUpdate(self, dds):
        val = self.GetPort0(dds) & 0xef
        self.SetPort0(dds, val)
        self.SetPort0(dds, val + 0x10)
        self.SetPort0(dds, val)


    # Read the value of a register
    # reg: the address of the register
    # return: the value of the register, in list of integers 0..255
    def ReadReg(self, dds, reg):
        if reg < 0 or reg > 23:
            raise ValueError(f"Invalid register: {reg}")
        ln = self.RegLen[reg]
        ret = ct.create_string_buffer(ln)
        self.DLL.AD9910_Read(dds, reg, ln, ct.byref(ret))
        return list(ret.raw)


    # Write data to a register
    # reg: the address of the register
    # dat: the data to write, in list of integers 0..255. its length should match the length of the corresponding register.
    def WriteReg(self, dds, reg, dat):
        if reg < 0 or reg > 23:
            raise ValueError(f"Invalid register: {reg}")
        ln = self.RegLen[reg]
        if len(dat) != ln:
            raise ValueError(f"Data length mismatch: {ln} is expected, " +
                             f"but {len(dat)} is given")
        buf = ct.create_string_buffer(bytes(dat))
        self.DLL.AD9910_Write(dds, reg, ln, ct.byref(buf))
        self.IOUpdate(dds)


    # Set the AuxDAC value of a DDS
    # val: AuxDAC value, 255 for maximum output power.
    def SetDACGain(self, dds, val):
        if val < 0 or val > 255:
            raise ValueError(f"Invalid value: {val}")
        reg = self.ReadReg(dds, 3)
        reg[3] = val
        self.WriteReg(dds, 3, reg)


    # Set reference mode of a DDS
    def SetExtRef(self, dds, ref):
        if ref == "1GHz":
            self.WriteReg(dds, 2, b"\x0d\x3f\xc0\xc8")
        elif ref == "10MHz":
            self.WriteReg(dds, 2, b"\x0d\x3f\xd9\x65")


    
    def GetCurrentProf(self, dds):
        return self.GetPort0(dds) & 7

    # Select current output profile of a DDS
    def SetCurrentProf(self, dds, prof):
        if prof < 0 or prof > 7:
            raise ValueError(f"Invalid profile: {prof}")
        self.DLL.AD9910_ActiveProfile(dds, prof)


    # Read a profile of a DDS
    # return:
    #   fre: frequency, in MHz
    #   amp: amplitude, 0 ~ 1
    #   pha: phase, 0 ~ 1, in unit of 2*pi
    def ReadProf(self, dds, prof):
        if prof < 0 or prof > 7:
            raise ValueError(f"Invalid profile: {prof}")
        self.SetCurrentProf(dds, prof)
        fre = ct.c_double(0)
        amp = ct.c_double(0)
        pha = ct.c_double(0)
        self.DLL.AD9910_ReadProfile(dds, prof,
                                    ct.byref(fre),
                                    ct.byref(amp),
                                    ct.byref(pha))
        return (fre.value, amp.value, pha.value)

    # Write a profile of a DDS, parameter definitions are the same as ReadProf
    def WriteProf(self, dds, prof, fre, amp, pha):
        if prof < 0 or prof > 7:
            raise ValueError(f"Invalid profile: {prof}")
        if fre < 0 or fre > 4e8:
            raise ValueError(f"Frequency out of range: {fre}")
        if amp < 0 or amp > 1:
            raise ValueError(f"Amplitude out of range: {amp}")
        self.DLL.AD9910_WriteProfile(dds, prof,
                                     ct.c_double(fre),
                                     ct.c_double(amp),
                                     ct.c_double(pha))
        self.SetCurrentProf(dds, 0)
        self.IOUpdate(dds)
